package demo;

import java.io.Closeable;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class EchoServer {

	private int port;

	private Selector selector;

	public EchoServer(int port) {
		this.port = port;
	}

	private void start() {
		ServerSocketChannel serverChannel = null;
		try {
			selector = Selector.open();
			serverChannel = ServerSocketChannel.open();
			serverChannel.configureBlocking(false);
			serverChannel.socket().bind(new InetSocketAddress(port));
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);

			Logger.getLogger(getClass().getName()).info("服务启动成功，监听端口：" + port);
		}
		catch (Exception e) {
			quietClose(selector);
			quietClose(serverChannel);
			throw new RuntimeException("服务启动失败！" + e.getMessage(), e);
		}
	}

	private void eventLoop() {
		try {
			while (true) {
				selector.select(); //阻塞操作
				Set<SelectionKey> keys = selector.selectedKeys();
				for (SelectionKey key : keys) {
					if (key.isAcceptable()) {
						onAcceptReady(key);
					}
					else {
						onSocketReady(key);
					}
				}
				keys.clear(); // 在下一次选择操作前清除ready集合
			}
		}
		catch (Exception e) {
			quietClose(selector);
			// 需要关闭serverChannel、channel吗？
			throw new RuntimeException("服务意外终止！" + e.getMessage(), e);
		}
	}

	private void onSocketReady(SelectionKey key) {
		SocketChannel channel = (SocketChannel) key.channel();
		ByteBuffer buffer = (ByteBuffer) key.attachment(); //buffer的状态是为写准备的

		try {
			if (key.isReadable()) {
				int len = channel.read(buffer); //尽最大能力读取，读取后，channel可能还有数据未读完 
				if (len < 0) {
					Logger.getLogger(getClass().getName())
						.info("Socket输入流已关闭。remote = " + channel.socket().getRemoteSocketAddress());

					key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); //关闭读事件
					if (buffer.position() == 0) {
						quietClose(channel);
					}
				}
				else if (buffer.position() > 0) {//有数据需要写
					key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
					logBuffer(buffer, buffer.position() - len, len);
				}
			}

			// key有可能在读事件中被cacel，所以要检测isValid
			if (key.isValid() && key.isWritable()) {
				buffer.flip();//切换为读状态
				channel.write(buffer);
				if (!buffer.hasRemaining()) {//如果已写完
					key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
					if ((key.interestOps() & SelectionKey.OP_READ) == 0) {
						quietClose(channel);
					}
				}
				buffer.compact();//将未写完的数据移到头部，切换为写状态
			}
		}
		catch (Exception e) {
			Logger.getLogger(getClass().getName()).log(Level.WARNING,
				"SocketChannel响应失败，关闭此通道。remote = " + channel.socket().getRemoteSocketAddress(), e);
			quietClose(channel);
		}
	}

	private void onAcceptReady(SelectionKey key) {
		SocketChannel channel = null;
		try {
			ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
			channel = serverChannel.accept(); //ServerSocketChannel为阻塞模式，此方法返回总是非null
			channel.configureBlocking(false); //SocketChannel为非阻塞模式
			ByteBuffer buffer = ByteBuffer.allocate(4096); //此通道的读写缓存，可一次性处理多个数据包
			buffer.put(("HTTP/1.1 200 OK\r\n"//
					+ "Content-Type: text/html;charset=UTF-8\r\n"//
					+ "\r\n").getBytes());
			channel.register(selector, SelectionKey.OP_READ, buffer);

			Logger.getLogger(getClass().getName()).info("客户端连接成功，远程地址：" + channel.socket().getRemoteSocketAddress());
		}
		catch (Exception e) {
			String remote = channel == null ? "null" : String.valueOf(channel.socket().getRemoteSocketAddress());
			Logger.getLogger(getClass().getName()).log(Level.WARNING, "响应accept失败，已忽略。remote = " + remote, e);
			quietClose(channel);
		}
	}

	/**
	 * 记录buffer内容
	 * 
	 * @param buffer
	 * @param index
	 *        开始位置
	 * @param len
	 *        记录长度
	 */
	private void logBuffer(ByteBuffer buffer, int index, int len) {
		if (len > 0) {
			try {
				ByteBuffer logBuf = (ByteBuffer) buffer.duplicate().position(index).limit(index + len);
				byte[] bs = new byte[len];
				logBuf.get(bs);
				Logger.getLogger(getClass().getName()).info("内容：" + new String(bs, "UTF-8"));
			}
			catch (Exception e) {
				Logger.getLogger(getClass().getName()).log(Level.WARNING, "日志记录失败：" + e.getMessage(), e);
			}
		}
	}

	private static void quietClose(Closeable closeable) {
		if (closeable != null) {
			try {
				closeable.close();
			}
			catch (Exception e) {
				//忽略关闭异常
			}
		}
	}

	/** JDK6 Selector未实现Closeable接口 */
	private static void quietClose(Selector selector) {
		if (selector != null) {
			try {
				selector.close();
			}
			catch (Exception e) {
				//忽略关闭异常
			}
		}
	}

	public static void main(String[] args) {
		EchoServer server = new EchoServer(7788);
		server.start();
		server.eventLoop();
	}
}
